#include "comm/my_str.h"
#include "comm/my_inet.h"
#include "comm/my_cleanup.h"
#include "comm/my_hooks.h"
#include "comm/my_handlers.h"
#include "event/my_conn.h"
#include "event/my_tcp.h"
#include "event/my_worker.h"
#include "event/my_event_loop.h"
#include "config/my_cache_conf.h"

static int64_t      task = 1;
static my_bool_t    use_et_model = MY_FALSE;

const char *s_msg = "hello client, i am server";

void my_test_server_read_done(my_event_t *event)
{
    my_conn_t *server_conn = event->data;

    my_syscall_result_t res;
    do {
        char data[1024];
        res = my_conn_read(server_conn, data, sizeof(data) - 1);
        if (res.res > 0) {
            data[(int)res.res] = '\0';
            MY_LOG_DEBUG("test_server read data: %s", data);
        }
    } while(res.res > 0);

    if (res.res == 0) {
        MY_LOG_DEBUG("test_server: client close");
        // fullthrough
    }

    if (res.res < 0 && res.eno == EAGAIN) {
        MY_LOG_DEBUG("test_server: read EAGAIN");
        // fullthrough
    }

    if (res.res < 0 && res.eno != EAGAIN) {
        MY_LOG_ERROR("test_server, read failed, errno :%d", res.eno);
        // fullthrough
    }

    my_event_loop_remove_conn(server_conn);
    my_conn_close(server_conn);
    my_thread_free(server_conn);
}

void my_test_server_read(my_event_t *event)
{
    my_conn_t *server_conn = event->data;

    my_syscall_result_t res;

    do {
        char data[1024];
        res = my_conn_read(server_conn, data, sizeof(data) - 1);
        if (res.res > 0) {
            data[(int)res.res] = '\0';
            MY_LOG_DEBUG("test_server read data: %s", data);
        }
    } while(res.res > 0);

    if (res.res == 0 || (res.eno != 0 && res.eno != EAGAIN)) {
        MY_LOG_ERROR("test_server: client close");
        my_event_loop_remove_conn(server_conn);
        my_conn_close(server_conn);
        my_thread_free(server_conn);
        return;
    }

    if (res.eno == EAGAIN) {
        MY_LOG_DEBUG("teset_server read EAGAIN");
    }

    res = my_conn_write(server_conn, (void *)s_msg, (int64_t)strlen(s_msg));
    if (res.res != (int64_t)strlen(s_msg)) {
        MY_LOG_FATAL("send failed, except: %d, but got %d. errno: %d", strlen(s_msg), res.res, res.eno);
    }

    server_conn->read.handler = my_test_server_read_done;
    my_event_loop_enabled_io(server_conn, MY_FALSE);
}

void my_test_server_accept_handler(my_event_t *event)
{
    my_conn_t *server_conn = event->data;

    server_conn->read.handler = my_test_server_read;
    my_event_loop_start_io(server_conn);
    // enabled read
    my_event_loop_enabled_io(server_conn, MY_FALSE);
}

void my_test_client_read_handler(my_event_t *event)
{
    my_conn_t *client_conn = event->data;

    my_syscall_result_t res;
    do {
        char data[1024];
        res = my_conn_read(client_conn, data, sizeof(data) - 1);
        if (res.res > 0) {
            data[(int)res.res] = '\0';
            MY_LOG_DEBUG("test_client read data: %s", data);
        }
    } while(res.res > 0);

    if (res.res == 0) {
        MY_LOG_ERROR("test_client: server close");
    }

    if (res.res < 0 && res.eno != EAGAIN) {
        MY_LOG_ERROR("test_client: error, errno: %d", res.eno);
    }

    if (res.res < 0 && res.eno == EAGAIN) {
        MY_LOG_DEBUG("test_client: read EAGAIN");
    }

    MY_LOG_DEBUG("client close");
    my_event_loop_remove_conn(client_conn);
    my_conn_close(client_conn);
    my_thread_free(client_conn);
}

const char *c_msg = "helloc server, i am client";

void my_test_client_write_handler(my_event_t *event)
{
    my_conn_t *client_conn = event->data;

    my_syscall_result_t res;
    res = my_conn_write(client_conn, (void *)c_msg, (int64_t)strlen(c_msg));
    if (res.res != (int64_t)strlen(c_msg)) {
        MY_LOG_FATAL("send failed, except: %d, but got %d. errno: %d", strlen(c_msg), res.res, res.eno);
    }

    // disabled write
    my_event_loop_disabled_io(client_conn, MY_TRUE);

    client_conn->read.handler = my_test_client_read_handler;
    // enabled read
    my_event_loop_enabled_io(client_conn, MY_FALSE);
}

void my_test_client_connect_handler(void *data, my_conn_t *conn, my_result_t res)
{
    if (res != MY_SUCCESS) {
        return;
    }

    conn->data = data;
    conn->write.handler = my_test_client_write_handler;
    my_event_loop_start_io(conn);
    my_event_loop_enabled_io(conn, MY_TRUE);
}

void my_test_client_handler(my_dispatch_event_t *dispatch_event)
{
    int data = (int)(intptr_t)dispatch_event->data;
    my_thread_free(dispatch_event);

    my_address_t address;
    bzero(&address, sizeof(my_address_t));
    if (!my_address_parser("127.0.0.1:9999", &address, NULL)) {
        MY_LOG_FATAL("parser %s failed", "127.0.0.1:9999");
    }

    my_tcp_connect_opts_t opts = {
        .nodelay = 1,
    };

    MY_LOG_DEBUG("test_client %d setup connect", data);
    my_tcp_connect_setup(&address, 10, &opts, my_test_client_connect_handler, (void *)(intptr_t)data);
}

void my_tcp_test()
{
    my_dispatch_event_t *dispatch_event;

    for (int i = 0; i < task; i++) {
        dispatch_event = my_thread_calloc(sizeof(my_dispatch_event_t));
        dispatch_event->handler = my_test_client_handler;
        dispatch_event->data = (void *)(intptr_t)i;
        my_worker_processor_post_event(dispatch_event);
    }
}

int main(int argc, char **argv)
{
    if (argc > 1 && strcmp(argv[1], "et") == 0) {
        use_et_model = MY_TRUE;
    }

    if (use_et_model) {
        MY_LOG_DEBUG("use ET model");
    } else {
        MY_LOG_DEBUG("use LT model");
    }

    my_hooks_init();

    my_cache_conf_init();

    my_tcp_listener_t *ls = my_thread_calloc(sizeof(my_tcp_listener_t));
    my_cleanup_handler_add(&g_cleanup_list, my_thread_free_handle, ls);
    my_tcp_listener_init(ls);
    ls->listen_addr = "127.0.0.1:9999";
    ls->backlog = 1024;
    ls->nodelay = 1;
    ls->reuseaddr = 1;
    ls->accept_handler = my_test_server_accept_handler;

    my_tcp_listener_register(ls);

    my_worker_processor_start();
    my_tcp_accepter_start();

    my_tcp_test();
    sleep(2);

    my_tcp_accepter_stop();
    my_worker_processor_stop();

    my_cleanup_list_exec(&g_cleanup_list);
    my_mem_assert_leak();

    MY_LOG_DEBUG("test pass");

    return 0;
}